#ifndef XG_REDISCONNECT_CPP
#define XG_REDISCONNECT_CPP
////////////////////////////////////////////////////////
#include "../RedisConnect.h"


string RedisConnect::Command::toString() const
{
	StringCreator out;

	out << "*" << vec.size() << "\r\n";

	for (const string& item : vec)
	{
		out << "$" << item.length() << "\r\n" << item << "\r\n";
	}

	return out.getContent();
}
int RedisConnect::Command::parse(const char* msg, int len)
{
	if (*msg == '$')
	{
		const char* end = parseNode(msg, len);

		if (end == NULL) return XG_DATAERR;

		switch (end - msg)
		{
			case 0: return XG_TIMEOUT;
			case -1: return XG_NOTFOUND;
		}

		return XG_OK;
	}

	const char* str = msg + 1;
	const char* end = strstr(str, "\r\n");

	if (end == NULL) return XG_TIMEOUT;

	if (*msg == '+' || *msg == '-' || *msg == ':')
	{
		this->status = XG_OK;
		this->msg = string(str, end);

		if (*msg == '+') return XG_OK;
		if (*msg == '-') return XG_ERROR;

		this->status = stdx::atoi(str);

		return XG_OK;
	}

	if (*msg == '*')
	{
		int cnt = stdx::atoi(str);
		const char* tail = msg + len;

		vec.clear();
		str = end + 2;

		while (cnt > 0)
		{
			if (*str == '*') return parse(str, tail - str);

			end = parseNode(str, tail - str);

			if (end == NULL) return XG_DATAERR;

			if (end == str) return XG_TIMEOUT;

			str = end;
			cnt--;
		}

		return res.size();
	}

	return XG_DATAERR;
}
const char* RedisConnect::Command::parseNode(const char* msg, int len)
{
	const char* str = msg + 1;
	const char* end = strstr(str, "\r\n");

	if (end == NULL) return msg;

	int sz = stdx::atoi(str);

	if (sz < 0) return msg + sz;

	str = end + 2;
	end = str + sz + 2;

	if (msg + len < end) return msg;

	res.push_back(string(str, str + sz));

	return end;
}
int RedisConnect::Command::getResult(RedisConnect* redis, int timeout)
{
	auto doWork = [&](){
		string msg = toString();
		Socket& sock = redis->sock;

		if (sock.write(msg.c_str(), msg.length()) < 0) return XG_NETERR;

		int len = 0;
		int delay = 0;
		int readed = 0;
		char* dest = redis->buffer.str();
		const int maxsz = redis->buffer.size();

		while (readed < maxsz)
		{
			if ((len = sock.read(dest + readed, maxsz - readed, false)) < 0) return len;

			if (len == 0)
			{
				delay += SOCKECT_RECVTIMEOUT;

				if (delay > timeout) return XG_TIMEOUT;
			}
			else
			{
				dest[readed += len] = 0;

				if ((len = parse(dest, readed)) == XG_TIMEOUT)
				{
					delay = 0;
				}
				else
				{
					return len;
				}
			}
		}

		return XG_PARAMERR;
	};

	status = 0;
	msg.clear();

	redis->code = doWork();

	if (redis->code < 0 && msg.empty())
	{
		switch (redis->code)
		{
		case XG_SYSERR:
			msg = "system error";
			break;
		case XG_NETERR:
			msg = "network error";
			break;
		case XG_DATAERR:
			msg = "protocol error";
			break;
		case XG_TIMEOUT:
			msg = "response timeout";
			break;
		case XG_NOTFOUND:
			msg = "element not found";
			break;
		default:
			msg = "unknown error";
			break;
		}
	}

	redis->status = status;
	redis->msg = msg;

	return redis->code;
}

bool RedisConnect::CanUse()
{
	return GetTemplate()->port > 0;
}
RedisConnect* RedisConnect::GetTemplate()
{
	XG_DEFINE_GLOBAL_VARIABLE(RedisConnect)
}
sp<RedisConnect> RedisConnect::Instance()
{
	return GetTemplate()->grasp();
}
void RedisConnect::Setup(const string& host, int port, const string& passwd, int timeout, int memsz)
{
	RedisConnect* redis = GetTemplate();

	redis->host = host;
	redis->port = port;
	redis->memsz = memsz;
	redis->passwd = passwd;
	redis->timeout = timeout;
}
sp<RedisConnect> RedisConnect::grasp() const
{
	static ResPool<RedisConnect> pool([&](){
		sp<RedisConnect> redis = newsp<RedisConnect>();

		if (redis->connect(host, port, timeout, memsz) && redis->auth(passwd)) return redis;

		return redis = NULL;
	});

	sp<RedisConnect> redis = pool.get();

	if (redis && redis->getErrorCode())
	{
		pool.disable(redis);

		return grasp();
	}

	return redis;
}
////////////////////////////////////////////////////////
#endif